﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX;
using System.Drawing;
using System.Diagnostics;
using System.Windows.Forms;

using System.IO;

namespace DirectX_CS
{
	public class SelectMesh : IManagedMaterial, IColorable, IRangeable, IRecreatable, IRenderCallable //IManagedVertexBuffer, IManagedIndexBuffer, IManagedBuffers
	{
		[PGBrowsable]
		public Range3d Range
		{
			get { return new Range3d(XMin, YMin, ZMin, XMax, YMax, ZMax); }
		}

		public PrimitiveManager ManagedRenderer = null;

		private static string replicaErrorsAndWarnings;
		private static bool showWarnings = false;
		protected string errorsAndWarnings = "";

		protected static int[] replicaMesh_Adjacency = null;
		protected static VertexBuffer vbForReplicaMesh = null;

		private static SelectMesh mOnlyObject = null;
		private object mSelected = null;
		protected static Mesh replicaMesh = null;
		protected static Mesh boundedMesh = null;
  		Texture selectMeshTexture;

		[PGBrowsable]
		public bool Visible
		{
			get { return preRenderEnabled; }
			set { preRenderEnabled = value; }
		}

		public void ManagerDestroyed(PrimitiveManager caller)
		{
			ManagedRenderer = null;
		}

		public SelectMesh(object soloObjectToSelect, bool createMeshes)
		{
			mOnlyObject = this;

			//String epath = AppDomain.CurrentDomain.BaseDirectory; //http://stackoverflow.com/questions/362790/what-is-the-best-way-to-determine-application-root-directory
			//epath = epath.Substring(0, epath.LastIndexOf("\\"));

			selectMeshTexture = TextureFromBMPResource("DirectX_CS", "Texture1.bmp", "Resources");		
			mMaterial = new Material();
			mMaterial.Ambient = Color.White;
			mMaterial.Diffuse = Color.White;

			mSelected = soloObjectToSelect;
			ObjectsEditor.Selected = soloObjectToSelect;
			PrimitiveManager.Register(this);
			//if (createMeshes)
			//	CreateReplicaMeshFrom(soloObjectToSelect);
			if (createMeshes) Recreate();			
		}

		public static Texture TextureFromBMPResource(string nspace, string bmpName, string folder)
		{
			System.Reflection.Assembly myAssembly = System.Reflection.Assembly.GetExecutingAssembly();
			string fullName = nspace + "." + folder + "." + bmpName;
			Stream myStream = myAssembly.GetManifestResourceStream(fullName);//myAssembly.GetManifestResourceStream(this.GetType(), texturename);
			Bitmap selectMeshTextureBMP = new Bitmap(myStream);
			Texture theTexture = new Texture(MainProgram.DxDevice, myStream, Usage.None, Pool.Managed);
			return theTexture;
		}

		private bool useBoundedSphereNotBox = false; //notably the sphere is good for spherical rotation.
		[PGBrowsable]
		public bool UseBoundedSphereNotBox
		{
			get { return useBoundedSphereNotBox; }
			set { useBoundedSphereNotBox = value; }
		}

		private float max(float a, float b, float c)
		{
			return Math.Max(Math.Max(a, b), c);
		}
		int sphereSlices = 8;
		[PGBrowsable]
		public int SphereSlices
		{
			get { return sphereSlices; }
			set { sphereSlices = value; }
		}
		int sphereStacks = 8;
		[PGBrowsable]
		public int SphereStacks
		{
			get { return sphereStacks; }
			set { sphereStacks = value; }
		}

		private void CreateBounded()
		{
			IRangeModifiable selRbl = SelectedRangeable;
			if (selRbl != null)
			{
				float width = selRbl.XMax - selRbl.XMin ;
				float height = selRbl.YMax - selRbl.YMin;
				float depth = selRbl.ZMax - selRbl.ZMin;

				bool useSphere = useBoundedSphereNotBox;
				float radius = 0;
				
				if (useBoundedSphereNotBox)
				{
					//float radius = max(width,height, depth);
					float w_2 = width / 2f; float h_2 = height / 2f; float d_2 = depth / 2f;
					//compute three possibilities and take the biggest one. We need to look at not the w,h, or depth but the hypotenuse of a bounding box, centered (hence the division by two).
					float r1 = new Vector2(w_2, h_2).Length();
					float r2 = new Vector2(w_2, d_2).Length();
					float r3 = new Vector2(h_2, d_2).Length();
					radius = max(r1, r2, r3);
					boundedMesh = Mesh.Sphere(MainProgram.DxDevice, radius, sphereSlices, sphereStacks);
				} 
				else
					boundedMesh = Mesh.Box(MainProgram.DxDevice,width,height, depth);

				//http://www.codegod.de/WebAppCodeGod/managed-directx-tutorial-meshes-AID149.aspx
				//it seems we need to recreate the vertex buffer once we set the initial values...
				Mesh tempMeshWithBuffer = boundedMesh.Clone(boundedMesh.Options.Value, CustomVertex.PositionColored.Format, MainProgram.DxDevice);
				boundedMesh.Dispose();
				boundedMesh = tempMeshWithBuffer;

				if (Selected is IModelTransformable )	  //&& Selected is IManagedVertexBuffer
				{																			 
					CustomVertex.PositionColored[] verts = (CustomVertex.PositionColored[])boundedMesh.VertexBuffer.Lock(
						0,
					   typeof(CustomVertex.PositionColored), 
						LockFlags.None, 
					   boundedMesh.NumberVertices);
					int colorForVertices = MainVertexColor.ToArgb();

					TransformMatrix mv = ((IModelTransformable)Selected).ModelView;

					////take a copy because we do not need to scale it we already have the dimensions	we need
					//mv.UseScaling = false;
					////also dont do a rotation on the selected mesh,... yet
					//mv.UseRotation = false;

					//Vector3 pos = mv.Position;
					//if (useSphere)
					//{
					//    //mv.Position = new Vector3(pos.X - radius / 2f, pos.Y - radius / 2f, pos.Z - radius / 2f);	 //doesnt work
					//}
					//else
					//{
					//    mv.Position = new Vector3(pos.X - width / 2f, pos.Y - height/ 2f, pos.Z - depth/ 2f);
					//}
					////we also need to tran the boundedmesh so subtract out half the length widht height radius, whatever on each dimension
					
					Matrix mvm = mv.RecreateMatrix();

					for (int i = 0; i < verts.Length; i++)
					{													
					   //now we can modify and see the vertex buffer
						verts[i].Color = colorForVertices;
						Vector3 vpos = verts[i].Position;
						vpos.TransformCoordinate(mvm);
						verts[i].Position = vpos;
					}

					boundedMesh.VertexBuffer.Unlock();
   				}

				SetBoundedMeshAttributeTable();
			}
			else
				boundedMesh = null;
			
		}
		public static SelectMesh OnlyObject
		{
			get { return mOnlyObject; }
		}
		protected Material mMaterial = new Material();
		Color mVertexColor = Color.Gray;   //should be used to change color during copy if option to be used is on.

		//float mZMin = 0, mZMax = 10; //should be maximum and minimum of underlying mesh which itself is based on the Irangeable of the selected primitiveWithVertices object.
		[PGBrowsable]
		public float ZMin
		{
			get
			{
				IRangeModifiable selRbl = SelectedRangeable;
				if (selRbl != null && selRbl != this)
					return selRbl.ZMin;
				else
					return 0;
			}
			//set {
			//    IRangeableOld selRbl = SelectedRangeable;
			//    if (selRbl != null && selRbl != this)
			//        selRbl.ZMin = value; 
			//}
		}
		[PGBrowsable]
		public float ZMax
		{
			get {
				IRangeModifiable selRbl = SelectedRangeable;
				if (selRbl != null && selRbl != this)
					return selRbl.ZMax;
				else
					return 0;
			}
			//set {
			//    IRangeableOld selRbl = SelectedRangeable;
			//    if (selRbl != null && selRbl != this)
			//        selRbl.ZMax = value;
			//}
		}


		[PGBrowsable]
		public float XMin
		{
			get
			{
				IRangeModifiable selRbl = SelectedRangeable;
				if (selRbl != null && selRbl != this)
					return selRbl.XMin;
				else return 0;
			}
			//set
			//{
			//    IRangeableOld selRbl = SelectedRangeable;
			//    if (selRbl != null && selRbl != this)
			//        selRbl.XMin = value;
			//}
		}
		[PGBrowsable]
		public float XMax
		{
			get
			{
				IRangeModifiable selRbl = SelectedRangeable;
				if (selRbl != null && selRbl != this)
					return selRbl.XMax;
				else return 0;
			}
			//set
			//{
			//    IRangeableOld selRbl = SelectedRangeable;
			//    if (selRbl != null && selRbl != this)
			//        selRbl.XMax = value;
			//}
		}
		[PGBrowsable]
		public float YMin
		{
			get
			{
				IRangeModifiable selRbl = SelectedRangeable;
				if (selRbl != null && selRbl != this)
					return selRbl.YMin;
				else return 0;
			}
			//set
			//{
			//    IRangeableOld selRbl = SelectedRangeable;
			//    if (selRbl != null && selRbl != this)
			//        selRbl.YMin = value;
			//}
		}
		[PGBrowsable]
		public float YMax
		{
			get
			{
				IRangeModifiable selRbl = SelectedRangeable;
				if (selRbl != null && selRbl != this)
					return selRbl.YMax;
				else return 0;
			}
			//set
			//{
			//    IRangeableOld selRbl = SelectedRangeable;
			//    if (selRbl != null && selRbl != this)
			//        selRbl.YMax = value;
			//}
		}
		[PGBrowsable]
		public Color MainVertexColor
		{
			get { return mVertexColor; }
			set
			{
				mVertexColor = value;
			}
		}
		[PGBrowsable]
		public object Selected
		{
			get { return mSelected; }
			set
			{
				if (value == this)
					mSelected = null; //cannot select itself it will do recurseive things..
										//best thing to do is unselect what we have going by setting to null the selected object
				else mSelected = value;
			}
		}
		
		public IRangeModifiable SelectedRangeable
		{
			get
			{
				object sel = Selected;
				if (sel == null)
					return null;
				else if (sel is IRangeModifiable)
					return (IRangeModifiable)sel;
				else return null;
			}
		}

		public void Recreate()
		{
			CreateBounded();
			//dont create the replica yet since its not working
			//CreateReplicaMeshFrom(soloObjectToSelect);
	}
		[PGBrowsable]
		public Material Material  
		{
			get { return mMaterial; }
			set { mMaterial = value; }
		}

		public Vector3 IntersectBoundedMesh(ref Vector3 farPoint, ref Vector3 nearPoint)
		{
			if (boundedMesh != null)
			{
				IntersectInformation ii;
				//IntersectInformation[] iiAll;
				Vector3 rayDir = nearPoint - farPoint;
				Vector3 destPoint = farPoint;
				if (boundedMesh.Intersect(farPoint, rayDir, out ii))
				{
					destPoint = farPoint + (rayDir * ii.Dist);
				}
				return destPoint;
			}
			else
			{
				return new Vector3(0, 0, 0);
			}
		}

		/// <summary>
		/// Procedure to create a mesh from an object with a vertex buffer to offer for rendering into a Direct3d.Mesh object.
		/// </summary>
		/// <param name="objectToMesh">IVertexBuffer object from which to create a "replica" looking Mesh object.</param>
		private void CreateReplicaMeshFrom(object objectToMesh)
		{
			if (objectToMesh != null)
			{
				if (objectToMesh is IModelTransformable)
				{
					IModelTransformable mv = (IModelTransformable)objectToMesh;

					if (mv.ModelView != null)
					{
						Matrix modelViewMatrix = mv.ModelView.RecreateMatrix();
						CreateReplicaMeshFrom(modelViewMatrix, objectToMesh);
					}

					else
						CreateReplicaMeshFrom(MainProgram.DxDevice.Transform.World, objectToMesh);
					//nothing we can do here...since obj is null

				}
			}
		}

		private void CreateReplicaMeshFrom(Matrix world, object selected)
		{
			Device dev = MainProgram.DxDevice;

			//if (selectMesh != null)
			//	selectMesh.Dispose();

			if (selected is IRangeModifiable)
			{   
				//NOW CREATE ONE MORE MESH WHICH WILL BE AN EXACT REPLICA OF THE VERTEX DATA -- this aint no bounding mesh
				replicaMesh = CreateExactReplicaMesh(selected);

				//FOR NOW SET REPLICA MESH TO NULL
				//replicaMesh = null;

				//set the attribute range 
				if (replicaMesh != null)
					SetReplicaMeshAttributeTable();

				IRangeModifiable r = (IRangeModifiable)selected;
				//l = rnd.XMax - rnd.XMin; w = rnd.YMax - rnd.YMin; h = rnd.ZMax - rnd.ZMin;

				////SelectBox = Mesh.Box(dev, l, w, h); //create a-new
				//selectMesh = new Mesh(12, 8, MeshFlags.Dynamic , SVector.Format, dev);

				// Set up the 8 corners of the cube
				float front = r.XMax; float back = r.XMin;
				float left = r.YMax; float right = r.YMin;
				float top = r.ZMax; float bottom = r.ZMin;

			/*

				int[] newAdjacency = null;
				//selectMesh_Adjacency = new int[theMesh.NumberFaces * 3];
				//theMesh.GenerateAdjacency(0.01F, selectMesh_Adjacency);
				int numFaces = 6; //in a cube
				selectMesh = Optimize(ref numFaces, ref selectMesh, out selectMesh_Adjacency, out newAdjacency, out errorsAndWarnings);
				selectMesh_Adjacency = newAdjacency; newAdjacency = null;
				//theMesh.OptimizeInPlace(MeshFlags.OptimizeVertexCache, selectMesh_Adjacency);
				
				//set the attribute range 
				if (selectMesh != null)
					SetBoundedMeshAttributeTable();
			*/



			}
		}
		private Mesh CreateExactReplicaMesh(object primitive)
		{
			Mesh newMeshReplica = null;
			if (primitive is IManagedBuffers)
			{
				IManagedBuffers mib = (IManagedBuffers)primitive;
				Array theVertices = (Array)mib.GetVertices();
				int numFaces = mib.GetIndicesCount() / 3;

				//think about recreating if necessary
				if (theVertices != null)
				{
	
					if (newMeshReplica == null) //dont create new each time
						newMeshReplica = new Mesh(numFaces, mib.GetVerticesCount(), MeshFlags.Dynamic, SVector.Format, MainProgram.DxDevice);//using overload 5/5

					IManagedBuffers myBuffer = (IManagedBuffers)primitive;
					CopyBufferToMesh(myBuffer, newMeshReplica); //we already checked compliance i guess with the top if statement


					int[] newAdjacency = null;
					int numFacesInReplica = myBuffer.NumberFaces;
					newMeshReplica = Clean(ref numFacesInReplica, ref newMeshReplica, out replicaMesh_Adjacency, out newAdjacency, out replicaErrorsAndWarnings);
					replicaMesh_Adjacency = newAdjacency; newAdjacency = null;

				} else newMeshReplica = null;
				return newMeshReplica;
			} 
			else
				return null;
		}

		//private static void CopyVerticesToMesh(IManagedBuffers primitiveWithVertices, Mesh meshToCopyTo)
		//{
		//    IManagedIndexBuffer mib = (IManagedIndexBuffer)primitiveWithVertices;
		//    IManagedVertexBuffer mib = (IManagedVertexBuffer)primitiveWithVertices;
		//    Array theVertices = (Array)mib.GetVertices();
		//    //object indices = mib.GetIndices();
		//    //int numFaces = mib.NumberFaces;

		//    CopyBufferToMesh(theVertices, meshToCopyTo);
		//}

		private static Array CopyBufferToMesh(IManagedBuffers primitiveWithVertices, Mesh meshToCopyTo)
		{
			IManagedIndexBuffer mib = (IManagedIndexBuffer)primitiveWithVertices;
			IManagedVertexBuffer miv = (IManagedVertexBuffer)primitiveWithVertices;
			Array objVertices = (Array)miv.GetVertices();
			object indices = mib.GetIndices();

			if (meshToCopyTo != null)
			{
				//== null) //dont create new each time
				//meshToCopyTo = new Mesh(numFaces, mib.GetVerticesCount(), MeshFlags.Dynamic, SVector.Format, MainProgram.DxDevice);//using overload 5/5

				Array vertexBufferData = meshToCopyTo.LockVertexBuffer(typeof(SVector), LockFlags.Discard, objVertices.Length);
				Array.Copy(objVertices, vertexBufferData, objVertices.Length);
				meshToCopyTo.UnlockVertexBuffer();

				Debug.WriteLine("make sure faces = indices count / 3, but lets try this. Also remember to add code that calls TransformVertexData");
				//newMeshReplica.SetVertexBufferData(objVertices, LockFlags.Discard); //of course discard because we are dynamic and regenerate each time.
				int indLen = ((Array)indices).Length;
				Array indexBufferData = meshToCopyTo.LockIndexBuffer(typeof(int), LockFlags.Discard, indLen);
				Array.Copy((Array)indices, indexBufferData, indLen);
				meshToCopyTo.UnlockIndexBuffer();

				//newMeshReplica.SetIndexBufferData(indices, LockFlags.Discard);
				//now unlock
				
				
			}
			return objVertices;

		}

		private static WeldEpsilons epsilon = new WeldEpsilons();

		private static Mesh Clean(ref int NumberFaces, ref Mesh theMesh, out int[] adjacency, out int[] newAdjacency, out string errors)
		{
			epsilon.Position = .01f;
			if (NumberFaces <= 0)
				NumberFaces = theMesh.NumberFaces;

			adjacency = new int[NumberFaces * 3];
			theMesh.GenerateAdjacency(0.01F, adjacency);
			if (showWarnings)   //validate and get whats wrong with the mesh
			{
				theMesh.Validate(adjacency, out errors);
				Debug.WriteLine("Replica-mesh validation of adjacency data reports > " + errors);
				Debug.Flush();
			}
			else
				errors = "";
									 
			int[] andBowTiesClean = null;
			// | CleanType.Simplification | CleanType.Optimization
			theMesh.Optimize(MeshFlags.Dynamic | MeshFlags.OptimizeCompact, adjacency);
			theMesh  = Mesh.Clean(CleanType.Optimization, theMesh, (int[])adjacency, out andBowTiesClean);

			//int[] simplified = null;
			//Mesh.Optimize(CleanType.Simplification, theMesh, andBowTiesClean, out simplified);

			//int[] optimized = null;
			//Mesh.Optimize(CleanType.Optimization, theMesh, simplified, out optimized);
			
			////theMesh.ComputeNormals();

			newAdjacency = andBowTiesClean;

			//Mesh.Simplify(theMesh,   adjacency,   0, MeshFlags.SimplifyVertex); //when uncommented produces error in the app..

			//theMesh.WeldVertices(WeldEpsilonsFlags.WeldAll, epsilon, newAdjacency);
			//theMesh.Optimize(MeshFlags.Dynamic, newAdjacency);  //when uncommented produces error in the app..
			if (showWarnings)   //validate and get whats wrong with the mesh
			{
				theMesh.Validate(newAdjacency, out errors);
				Debug.WriteLine("Replica-mesh validation of adjacency data reports (#2 AFTER): " + errors);
				Debug.Flush();
			}
			else
				errors = "";


			//theMesh.Optimize(MeshFlags.OptimizeVertexCache, adjacency);
			//theMesh.Optimize(MeshFlags.OptimizeVertexCache, newAdjacency);
			return theMesh;

		}

		public void ReplicaMesh_DrawSubset()
		{
			if (replicaMesh != null)
				replicaMesh.DrawSubset(replicaMeshAttributeRange.AttributeId);   //0 is of course the attribute id of the attributerange, specified
		}
		public void BoundedMesh_DrawSubset()
		{
			if (boundedMesh != null)
				boundedMesh.DrawSubset(boundedMeshAttributeRange.AttributeId);   //0 is of course the attribute id of the attributerange, specified
		}
		AttributeRange[] boundedMesh_AR_Array = new AttributeRange[1];
		AttributeRange boundedMeshAttributeRange = new AttributeRange();

		AttributeRange[] replicaMesh_AR_Array = new AttributeRange[1];
		AttributeRange replicaMeshAttributeRange = new AttributeRange();

		private void SetBoundedMeshAttributeTable()
		{
			boundedMeshAttributeRange.AttributeId = 0; //DONT FORGET THIS
			boundedMeshAttributeRange.VertexStart = 0;
			boundedMeshAttributeRange.VertexCount = boundedMesh.NumberVertices;
			boundedMeshAttributeRange.FaceStart = 0;
			boundedMeshAttributeRange.FaceCount = boundedMesh.NumberFaces;
			boundedMesh_AR_Array[0] = boundedMeshAttributeRange;
			boundedMesh.LockAttributeBufferArray(LockFlags.None);
			boundedMesh.SetAttributeTable(boundedMesh_AR_Array);
			boundedMesh.UnlockAttributeBuffer();
		}
		/// <summary>
		/// Requires replica mesh is not null.
		/// </summary>
		private void SetReplicaMeshAttributeTable()
		{
			replicaMeshAttributeRange.AttributeId = 0;
			replicaMeshAttributeRange.VertexStart = 0; 
			replicaMeshAttributeRange.VertexCount = replicaMesh.NumberVertices;
			replicaMeshAttributeRange.FaceStart = 0; 
			replicaMeshAttributeRange.FaceCount = replicaMesh.NumberFaces;
			replicaMesh_AR_Array[0] = replicaMeshAttributeRange;
			replicaMesh.LockAttributeBufferArray(LockFlags.Discard);
			replicaMesh.SetAttributeTable(replicaMesh_AR_Array);
			replicaMesh.UnlockAttributeBuffer();
		}

		#region IManagedIndexBuffer Members


		#endregion

		#region IRenderCallable Members
		protected bool preRenderEnabled = false;
		[PGBrowsable]
		public bool PreRenderEnabled
		{
			get
			{
				return preRenderEnabled;
			}
			set
			{
				preRenderEnabled = value;
			}
		}
		[PGBrowsable]
		public bool PostRenderEnabled
		{
			get
			{
				return false;
			}
			set
			{
				//do nothing //throw new NotImplementedException();
			}
		}


		public void PreRender()
		{

			object sel = Selected;

			if (sel != null)
			{
				//IModelTransformable mv = sel as IModelTransformable;
				MainProgram.DxDevice.Material = mMaterial;

				FillMode oldMode = MainProgram.DxDevice.RenderState.FillMode;
				MainProgram.DxDevice.RenderState.FillMode = FillMode.WireFrame;
				//MainProgram.DxDevice.SetTexture(0, selectMeshTexture);

				BoundedMesh_DrawSubset();

				//MainProgram.DxDevice.SetTexture(0, null);
				MainProgram.DxDevice.RenderState.FillMode = oldMode;
			}

		}

		public void PostRender()
		{
			
		}

		#endregion


	}
}
